﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace PubSubLite
{
    internal class SubscribersStorage : ISubscribersStorage
    {
        private Dictionary<Type, Dictionary<Guid, ISubscriptionBase>> _subscriptions = new Dictionary<Type, Dictionary<Guid, ISubscriptionBase>>();
        private Dictionary<Type, Dictionary<string, int>> _typeSubscribers = new Dictionary<Type,Dictionary<string,int>>();

        private static SubscribersStorage _subscribers;
        internal static ISubscribersStorage Default
        {
            get
            {
                if (_subscribers == null)
                {
                    _subscribers = new SubscribersStorage();
                }
                return _subscribers;
            }
        }

        private string GetUniqueKey(object obj)
        {
            return string.Format("{0}-{1}", obj.GetType().FullName, obj.GetHashCode());
        }

        public void AddSubscriber<T>(ISubscription<T> subscription)
        {
            if(subscription == null)
            {
                throw new ArgumentNullException("subscription cannot be null");
            }
            if (!_subscriptions.ContainsKey(typeof(T)))
            {
                lock (this)
                {
                    _subscriptions.Add(typeof(T), new Dictionary<Guid, ISubscriptionBase>());
                    _typeSubscribers.Add(typeof(T), new Dictionary<string, int>());
                }
            }
            if(!_typeSubscribers[typeof(T)].ContainsKey(GetUniqueKey(subscription.Subscriber)))
            {
                lock (this)
                {
                    _subscriptions[typeof(T)].Add(subscription.ID, subscription);
                    _typeSubscribers[typeof(T)].Add(GetUniqueKey(subscription.Subscriber), 1);
                }
            }
        }

        public void RemoveSubscription(Guid id)
        {
            foreach (var type in _subscriptions.Keys)
            {
                if (_subscriptions[type].ContainsKey(id))
                {
                    var subscription = _subscriptions[type][id];
                    lock (this)
                    {
                        _subscriptions[type].Remove(id);
                        _typeSubscribers[type].Remove(GetUniqueKey(subscription.Subscriber));
                    }
                }
            }
        }

        public void Clear()
        {
            lock (this)
            {
                _subscriptions.Clear();
                _typeSubscribers.Clear();
            }
        }

        public IEnumerable<ISubscription<T>> GetSubscribers<T>()
        {
            var typedSubscriptions = new List<ISubscription<T>>();
            if (!_subscriptions.ContainsKey(typeof(T)))
            {
                return typedSubscriptions;
            }
            foreach(var subscription in _subscriptions[typeof(T)].Values)
            {
                typedSubscriptions.Add((ISubscription<T>)subscription);
            }
            return typedSubscriptions;
        }

        ICollection ISubscribersStorage.GetSubscribersByType(Type type)
        {
            var returnList = new ArrayList();
            if (!_subscriptions.ContainsKey(type))
            {
                return returnList;
            }
            foreach (var subscription in _subscriptions[type].Values)
            {
                returnList.Add(subscription);
            }
            return returnList;
        }

        private Dictionary<Guid, Delegate> _callbacks = new Dictionary<Guid, Delegate>();

        void ISubscribersStorage.AddCommandCallback<T>(Guid messageID, Action<T> callback)
        {
            lock (this)
            {
                _callbacks.Add(messageID, callback);
            }
        }

        Delegate ISubscribersStorage.GetCommandCallback(Guid messageID)
        {
            if (!_callbacks.ContainsKey(messageID))
            {
                throw new InvalidOperationException(string.Format("No callback was found for messageID {0}", messageID));
            }
            var callback = _callbacks[messageID];
            lock (this)
            {
                _callbacks.Remove(messageID);
            }
            return callback;
        }

       
    }
}
